// Gmsh - Copyright (C) 1997-2019 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// issues on https://gitlab.onelab.info/gmsh/gmsh/issues.

#include "GmshConfig.h"
#include "GmshMessage.h"
#include "GModel.h"
#include "GmshDefines.h"
#include "StringUtils.h"
#include "Context.h"
#include "Options.h"
#include "OpenFile.h"
#include "CreateFile.h"
#include "OS.h"

#if defined(HAVE_OPENGL)
#include "drawContext.h"
#endif

#if defined(HAVE_FLTK)
#include "FlGui.h"
#include "graphicWindow.h"
#include "openglWindow.h"
#include "gl2ps.h"
#include "gl2gif.h"
#include "gl2jpeg.h"
#include "gl2png.h"
#include "gl2ppm.h"
#include "gl2yuv.h"
#include "gl2pgf.h"
#endif

int GetFileFormatFromExtension(const std::string &ext, double *version)
{
    if(ext == ".geo_unrolled")  return FORMAT_GEO;
    else if(ext == ".auto")     return FORMAT_AUTO;
    else if(ext == ".msh")      return FORMAT_MSH;
    else if(ext == ".msh1")     { if(version) *version = 1.0; return FORMAT_MSH; }
    else if(ext == ".msh2")     { if(version) *version = 2.2; return FORMAT_MSH; }
    else if(ext == ".msh22")    { if(version) *version = 2.2; return FORMAT_MSH; }
    else if(ext == ".msh3")     { if(version) *version = 3.0; return FORMAT_MSH; }
    else if(ext == ".msh4")     { if(version) *version = 4.1; return FORMAT_MSH; }
    else if(ext == ".msh40")    { if(version) *version = 4.0; return FORMAT_MSH; }
    else if(ext == ".msh41")    { if(version) *version = 4.1; return FORMAT_MSH; }
    else if(ext == ".x3d")      return FORMAT_X3D;
    else if(ext == ".pos")      return FORMAT_POS;
    else if(ext == ".pvtu")     return FORMAT_PVTU;
    else if(ext == ".opt")      return FORMAT_OPT;
    else if(ext == ".unv")      return FORMAT_UNV;
    else if(ext == ".vtk")      return FORMAT_VTK;
    else if(ext == ".m")        return FORMAT_MATLAB;
    else if(ext == ".dat")      return FORMAT_TOCHNOG;
    else if(ext == ".txt")      return FORMAT_TXT;
    else if(ext == ".stl")      return FORMAT_STL;
    else if(ext == ".cgns")     return FORMAT_CGNS;
    else if(ext == ".med")      return FORMAT_MED;
    else if(ext == ".rmed")     return FORMAT_RMED;
    else if(ext == ".ir3")      return FORMAT_IR3;
    else if(ext == ".mesh")     return FORMAT_MESH;
    else if(ext == ".mail")     return FORMAT_MAIL;
    else if(ext == ".bdf")      return FORMAT_BDF;
    else if(ext == ".diff")     return FORMAT_DIFF;
    else if(ext == ".inp")      return FORMAT_INP;
    else if(ext == ".key")      return FORMAT_KEY;
    else if(ext == ".celum")    return FORMAT_CELUM;
    else if(ext == ".su2")      return FORMAT_SU2;
    else if(ext == ".nas")      return FORMAT_BDF;
    else if(ext == ".p3d")      return FORMAT_P3D;
    else if(ext == ".wrl")      return FORMAT_VRML;
    else if(ext == ".vrml")     return FORMAT_VRML;
    else if(ext == ".ply2")     return FORMAT_PLY2;
    else if(ext == ".gif")      return FORMAT_GIF;
    else if(ext == ".jpg")      return FORMAT_JPEG;
    else if(ext == ".jpeg")     return FORMAT_JPEG;
    else if(ext == ".mpg")      return FORMAT_MPEG;
    else if(ext == ".mpeg")     return FORMAT_MPEG;
    else if(ext == ".png")      return FORMAT_PNG;
    else if(ext == ".pgf")      return FORMAT_PGF;
    else if(ext == ".ps")       return FORMAT_PS;
    else if(ext == ".eps")      return FORMAT_EPS;
    else if(ext == ".pdf")      return FORMAT_PDF;
    else if(ext == ".tex")      return FORMAT_TEX;
    else if(ext == ".svg")      return FORMAT_SVG;
    else if(ext == ".tikz")     return FORMAT_TIKZ;
    else if(ext == ".ppm")      return FORMAT_PPM;
    else if(ext == ".yuv")      return FORMAT_YUV;
    else if(ext == ".brep")     return FORMAT_BREP;
    else if(ext == ".step")     return FORMAT_STEP;
    else if(ext == ".stp")      return FORMAT_STEP;
    else if(ext == ".iges")     return FORMAT_IGES;
    else if(ext == ".igs")      return FORMAT_IGES;
    else if(ext == ".neu")      return FORMAT_NEU;
    else                        return -1;
}

int GuessFileFormatFromFileName(const std::string &fileName, double *version)
{
    std::string ext = SplitFileName(fileName)[2];
    return GetFileFormatFromExtension(ext, version);
}

std::string GetDefaultFileExtension(int format, bool onlyMeshFormats)
{
    std::string name;
    bool mesh = false;
    switch(format){
    case FORMAT_GEO:     name = ".geo_unrolled"; break;
    case FORMAT_MSH:     name = ".msh"; mesh = true; break;
    case FORMAT_POS:     name = ".pos"; break;
    case FORMAT_X3D:     name = ".x3d"; mesh = true; break;
    case FORMAT_PVTU:    name = ".pvtu"; break;
    case FORMAT_OPT:     name = ".opt"; break;
    case FORMAT_UNV:     name = ".unv"; mesh = true; break;
    case FORMAT_VTK:     name = ".vtk"; mesh = true; break;
    case FORMAT_MATLAB:  name = ".m"; mesh = true; break;
    case FORMAT_TOCHNOG: name = ".dat"; mesh = true; break;
    case FORMAT_STL:     name = ".stl"; mesh = true; break;
    case FORMAT_CGNS:    name = ".cgns"; mesh = true; break;
    case FORMAT_MED:     name = ".med"; mesh = true; break;
    case FORMAT_RMED:    name = ".rmed"; break;
    case FORMAT_IR3:     name = ".ir3"; mesh = true; break;
    case FORMAT_MESH:    name = ".mesh"; mesh = true; break;
    case FORMAT_MAIL:    name = ".mail"; mesh = true; break;
    case FORMAT_BDF:     name = ".bdf"; mesh = true; break;
    case FORMAT_DIFF:    name = ".diff"; mesh = true; break;
    case FORMAT_INP:     name = ".inp"; mesh = true; break;
    case FORMAT_KEY:     name = ".key"; mesh = true; break;
    case FORMAT_CELUM:   name = ".celum"; mesh = true; break;
    case FORMAT_SU2:     name = ".su2"; mesh = true; break;
    case FORMAT_P3D:     name = ".p3d"; mesh = true; break;
    case FORMAT_VRML:    name = ".wrl"; mesh = true; break;
    case FORMAT_PLY2:    name = ".ply2"; mesh = true; break;
    case FORMAT_GIF:     name = ".gif"; break;
    case FORMAT_JPEG:    name = ".jpg"; break;
    case FORMAT_MPEG:    name = ".mpg"; break;
    case FORMAT_PNG:     name = ".png"; break;
    case FORMAT_PGF:     name = ".pgf"; break;
    case FORMAT_PS:      name = ".ps"; break;
    case FORMAT_EPS:     name = ".eps"; break;
    case FORMAT_PDF:     name = ".pdf"; break;
    case FORMAT_TEX:     name = ".tex"; break;
    case FORMAT_SVG:     name = ".svg"; break;
    case FORMAT_TIKZ:    name = ".tikz"; break;
    case FORMAT_PPM:     name = ".ppm"; break;
    case FORMAT_YUV:     name = ".yuv"; break;
    case FORMAT_BREP:    name = ".brep"; break;
    case FORMAT_IGES:    name = ".iges"; break;
    case FORMAT_STEP:    name = ".step"; break;
    case FORMAT_NEU:     name = ".neu"; mesh = true; break;
    default:             name = ""; break;
    }
    if(onlyMeshFormats && !mesh) return "";
    return name;
}

std::string GetDefaultFileName(int format)
{
    std::vector<std::string> split = SplitFileName(GModel::current()->getFileName());
    std::string name = split[0] + split[1];
    std::string ext = GetDefaultFileExtension(format);
    return name + ext;
}

std::string GetKnownFileFormats(bool onlyMeshFormats)
{
    std::string all = "auto, msh1, msh2, msh22, msh3, msh4, msh40, msh41";
    for(int i = 1; i < 1000; i++){
        std::string ext = GetDefaultFileExtension(i, onlyMeshFormats);
        if(ext.size() > 1){
            all += ", " + ext.substr(1);
        }
    }
    return all;
}

#if defined(HAVE_FLTK)
static PixelBuffer *GetCompositePixelBuffer(GLenum format, GLenum type)
{
    openglWindow *newg = 0;

    if(CTX::instance()->print.width > 0 || CTX::instance()->print.height > 0){
        GLint width = FlGui::instance()->getCurrentOpenglWindow()->pixel_w();
        GLint height = FlGui::instance()->getCurrentOpenglWindow()->pixel_h();
        if(CTX::instance()->print.width <= 0){
            double w = width * CTX::instance()->print.height / (double)height;
            width = (int)w;
            height = CTX::instance()->print.height;
        }
        else if(CTX::instance()->print.height <= 0){
            double h = height * CTX::instance()->print.width / (double)width;
            height = (int)h;
            width = CTX::instance()->print.width;
        }
        else{
            width = CTX::instance()->print.width;
            height = CTX::instance()->print.height;
        }
        newg = new openglWindow(100, 100, width, height);
        int mode = FL_RGB | FL_DEPTH | (CTX::instance()->db ? FL_DOUBLE : FL_SINGLE);
        if(CTX::instance()->antialiasing) mode |= FL_MULTISAMPLE;
        newg->mode(mode);
        newg->end();
        newg->getDrawContext()->copyViewAttributes
                (FlGui::instance()->getCurrentOpenglWindow()->getDrawContext());
        newg->show();
        openglWindow::setLastHandled(newg);
        // waiting for the OS to really make the window visible and to call the
        // draw() function on (some ?) linux; if we do not wait here, the window is
        // not ready and the picture cannot be generated
        while(!newg->valid()) Fl::wait();
    }

    PixelBuffer *buffer;
    if(newg || !CTX::instance()->print.compositeWindows){
        GLint width = FlGui::instance()->getCurrentOpenglWindow()->pixel_w();
        GLint height = FlGui::instance()->getCurrentOpenglWindow()->pixel_h();
        buffer = new PixelBuffer(width, height, format, type);
        buffer->fill(CTX::instance()->batch);
    }
    else{
        graphicWindow *g = FlGui::instance()->graph[0];
        for(std::size_t i = 1; i < FlGui::instance()->graph.size(); i++){
            for(std::size_t j = 0; j < FlGui::instance()->graph[i]->gl.size(); j++){
                if(FlGui::instance()->graph[i]->gl[j] ==
                        FlGui::instance()->getCurrentOpenglWindow()){
                    g = FlGui::instance()->graph[i];
                    break;
                }
            }
        }
        int ww = 0, hh = 0;
        std::vector<PixelBuffer*> buffers;
        for(std::size_t i = 0; i < g->gl.size(); i++){
            openglWindow::setLastHandled(g->gl[i]);
            buffer = new PixelBuffer(g->gl[i]->pixel_w(), g->gl[i]->pixel_h(), format, type);
            buffer->fill(CTX::instance()->batch);
            buffers.push_back(buffer);
            ww = std::max(ww, g->gl[i]->x() + g->gl[i]->pixel_w());
            hh = std::max(hh, g->gl[i]->y() + g->gl[i]->pixel_h());
        }
        buffer = new PixelBuffer(ww, hh, format, type);
        for(std::size_t i = 0; i < g->gl.size(); i++){
            buffer->copyPixels(g->gl[i]->x(), hh - g->gl[i]->h() - g->gl[i]->y(),
                               buffers[i]);
            delete buffers[i];
        }
    }

    if(newg){
        openglWindow::setLastHandled(0);
        newg->hide();
        delete newg;
    }

    return buffer;
}
#endif

void change_print_parameter(int frame)
{
    double first = CTX::instance()->print.parameterFirst;
    double last = CTX::instance()->print.parameterLast;
    double steps = CTX::instance()->print.parameterSteps - 1;
    if(steps <= 0) steps = 1;
    double step = (last - first) / steps;
    double v = first + frame * step * CTX::instance()->post.animStep;
    Msg::Info("Setting Print.Parameter = %g", v);
    opt_print_parameter(0, GMSH_SET | GMSH_GUI, v);
    ParseString(CTX::instance()->print.parameterCommand, true);
}

void CreateOutputFile(const std::string &fileName, int format,
                      bool status, bool redraw)
{
    std::string name = fileName;
    if(name.empty()) name = GetDefaultFileName(format);

    int oldFormat = CTX::instance()->print.fileFormat;
    CTX::instance()->print.fileFormat = format;
    CTX::instance()->printing = 1;
    bool error = false;

    if(status)
        Msg::StatusBar(true, "Writing '%s'...", name.c_str());

    switch (format) {

    case FORMAT_AUTO:
    {
        double version = 0.;
        int format = GuessFileFormatFromFileName(name, &version);
        if(format == FORMAT_MSH && version > 0.)
            CTX::instance()->mesh.mshFileVersion = version;
        CreateOutputFile(name, format, false, false);
    }
        break;

    case FORMAT_OPT:
        PrintOptions(0, GMSH_FULLRC, 1, 1, name.c_str());
        break;

    case FORMAT_MSH:
        if(GModel::current()->getNumPartitions() &&
                CTX::instance()->mesh.partitionSplitMeshFiles){
            std::vector<std::string> splitName = SplitFileName(name);
            splitName[0] += splitName[1];
            GModel::current()->writePartitionedMSH
                    (splitName[0], CTX::instance()->mesh.mshFileVersion,
                    CTX::instance()->mesh.binary, CTX::instance()->mesh.saveAll,
                    CTX::instance()->mesh.saveParametric, CTX::instance()->mesh.scalingFactor);
        }
        else{
            GModel::current()->writeMSH
                    (name, CTX::instance()->mesh.mshFileVersion, CTX::instance()->mesh.binary,
                     CTX::instance()->mesh.saveAll, CTX::instance()->mesh.saveParametric,
                     CTX::instance()->mesh.scalingFactor);
        }
        if(GModel::current()->getNumPartitions() &&
                CTX::instance()->mesh.partitionSaveTopologyFile){
            std::vector<std::string> splitName = SplitFileName(name);
            splitName[0] += splitName[1] + "_topology.pro";
            GModel::current()->writePartitionedTopology(splitName[0]);
        }
        break;

    case FORMAT_STL:
        GModel::current()->writeSTL
                (name, CTX::instance()->mesh.binary, CTX::instance()->mesh.saveAll,
                 CTX::instance()->mesh.scalingFactor,
                 CTX::instance()->mesh.stlOneSolidPerSurface);
        break;

    case FORMAT_VRML:
        GModel::current()->writeVRML
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_PLY2:
        GModel::current()->writePLY2(name);
        break;

    case FORMAT_UNV:
        GModel::current()->writeUNV
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.saveGroupsOfNodes,
                 CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_TOCHNOG:
        GModel::current()->writeTOCHNOG
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_VTK:
        GModel::current()->writeVTK
                (name, CTX::instance()->mesh.binary, CTX::instance()->mesh.saveAll,
                 CTX::instance()->mesh.scalingFactor,
                 CTX::instance()->bigEndian);
        break;

    case FORMAT_MATLAB:
        GModel::current()->writeMATLAB
                (name, CTX::instance()->mesh.binary, CTX::instance()->mesh.saveAll,
                 CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_MESH:
        GModel::current()->writeMESH
                (name, CTX::instance()->mesh.saveElementTagType,
                 CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_MAIL:
        GModel::current()->writeMAIL
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_IR3:
        GModel::current()->writeIR3
                (name, CTX::instance()->mesh.saveElementTagType,
                 CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_BDF:
        GModel::current()->writeBDF
                (name, CTX::instance()->mesh.bdfFieldFormat,
                 CTX::instance()->mesh.saveElementTagType, CTX::instance()->mesh.saveAll,
                 CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_DIFF:
        GModel::current()->writeDIFF
                (name, CTX::instance()->mesh.binary, CTX::instance()->mesh.saveAll,
                 CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_INP:
        GModel::current()->writeINP
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.saveGroupsOfNodes,
                 CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_KEY:
        GModel::current()->writeKEY
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.saveGroupsOfNodes,
                 CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_CELUM:
        GModel::current()->writeCELUM
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_SU2:
        GModel::current()->writeSU2
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_P3D:
        GModel::current()->writeP3D
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_CGNS:
        GModel::current()->writeCGNS
                (name, CTX::instance()->mesh.zoneDefinition, CTX::instance()->cgnsOptions,
                 CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_MED:
        GModel::current()->writeMED
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_POS:
        GModel::current()->writePOS
                (name, CTX::instance()->print.posElementary,
                 CTX::instance()->print.posElement,
                 CTX::instance()->print.posSICN, CTX::instance()->print.posSIGE,
                 CTX::instance()->print.posGamma, CTX::instance()->print.posDisto,
                 CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

    case FORMAT_GEO:
        GModel::current()->writeGEO(name, CTX::instance()->print.geoLabels,
                                    CTX::instance()->print.geoOnlyPhysicals);
        break;

    case FORMAT_BREP:
        GModel::current()->writeOCCBREP(name);
        break;

    case FORMAT_STEP:
        GModel::current()->writeOCCSTEP(name);
        break;

    case FORMAT_NEU:
        GModel::current()->writeNEU
                (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor);
        break;

#if defined(HAVE_FLTK)
    case FORMAT_PPM:
    case FORMAT_YUV:
    case FORMAT_GIF:
    case FORMAT_JPEG:
    case FORMAT_PNG:
    {
        if(!FlGui::available()){
            Msg::Error("Creating '%s' requires a graphical interface context", name.c_str());
            break;
        }

        FILE *fp = Fopen(name.c_str(), "wb");
        if(!fp){
            Msg::Error("Unable to open file '%s'", name.c_str());
            error = true;
            break;
        }

        PixelBuffer *buffer = GetCompositePixelBuffer(GL_RGB, GL_UNSIGNED_BYTE);

        if(format == FORMAT_PPM)
            create_ppm(fp, buffer);
        else if(format == FORMAT_YUV)
            create_yuv(fp, buffer);
        else if(format == FORMAT_GIF)
            create_gif(fp, buffer,
                       CTX::instance()->print.gifDither,
                       CTX::instance()->print.gifSort,
                       CTX::instance()->print.gifInterlace,
                       CTX::instance()->print.gifTransparent);
        else if(format == FORMAT_JPEG)
            create_jpeg(fp, buffer, CTX::instance()->print.jpegQuality,
                        CTX::instance()->print.jpegSmoothing);
        else
            create_png(fp, buffer, 100);

        delete buffer;
        fclose(fp);
    }
        break;

    case FORMAT_PS:
    case FORMAT_EPS:
    case FORMAT_PDF:
    case FORMAT_SVG:
    case FORMAT_TIKZ:
    {
        if(!FlGui::available()){
            Msg::Error("Creating '%s' requires a graphical interface context", name.c_str());
            break;
        }

        FILE *fp = Fopen(name.c_str(), "wb");
        if(!fp){
            Msg::Error("Unable to open file '%s'", name.c_str());
            error = true;
            break;
        }
        std::string base = SplitFileName(name)[1];
        GLint width = FlGui::instance()->getCurrentOpenglWindow()->pixel_w();
        GLint height = FlGui::instance()->getCurrentOpenglWindow()->pixel_h();
        GLint pixel_viewport[4] = {0, 0, width, height};

        PixelBuffer buffer(width, height, GL_RGB, GL_FLOAT);

        if(CTX::instance()->print.epsQuality == 0)
            buffer.fill(CTX::instance()->batch);

        int psformat =
                (format == FORMAT_PDF) ? GL2PS_PDF :
                                         (format == FORMAT_PS) ? GL2PS_PS :
                                                                 (format == FORMAT_SVG) ? GL2PS_SVG :
                                                                                          (format == FORMAT_TIKZ) ? GL2PS_PGF :
                                                                                                                    GL2PS_EPS;
        int pssort =
                (CTX::instance()->print.epsQuality == 3) ? GL2PS_NO_SORT :
                                                           (CTX::instance()->print.epsQuality == 2) ? GL2PS_BSP_SORT :
                                                                                                      GL2PS_SIMPLE_SORT;
        int psoptions =
                GL2PS_SIMPLE_LINE_OFFSET | GL2PS_SILENT |
                (CTX::instance()->print.epsOcclusionCulling ? GL2PS_OCCLUSION_CULL : 0) |
                (CTX::instance()->print.epsBestRoot ? GL2PS_BEST_ROOT : 0) |
                (CTX::instance()->print.background ? GL2PS_DRAW_BACKGROUND : 0) |
                (CTX::instance()->print.epsCompress ? GL2PS_COMPRESS : 0) |
                (CTX::instance()->print.epsPS3Shading ? 0 : GL2PS_NO_PS3_SHADING);

        GLint buffsize = 0;
        int res = GL2PS_OVERFLOW;
        while(res == GL2PS_OVERFLOW) {
            buffsize += 2048 * 2048;
            gl2psBeginPage(base.c_str(), "Gmsh", pixel_viewport,
                           psformat, pssort, psoptions, GL_RGBA, 0, nullptr,
                           15, 20, 10, buffsize, fp, base.c_str());
            if(CTX::instance()->print.epsQuality == 0){
                double modelview[16], projection[16];
                glGetDoublev(GL_PROJECTION_MATRIX, projection);
                glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
                glMatrixMode(GL_PROJECTION);
                glLoadIdentity();
                glOrtho((double)pixel_viewport[0], (double)pixel_viewport[2],
                        (double)pixel_viewport[1], (double)pixel_viewport[3], -1., 1.);
                glMatrixMode(GL_MODELVIEW);
                glLoadIdentity();
                glRasterPos2d(0, 0);
                gl2psDrawPixels(width, height, 0, 0, GL_RGB, GL_FLOAT, buffer.getPixels());
                glMatrixMode(GL_PROJECTION);
                glLoadMatrixd(projection);
                glMatrixMode(GL_MODELVIEW);
                glLoadMatrixd(modelview);
            }
            else{
                drawContext::global()->drawCurrentOpenglWindow(true);
            }
            res = gl2psEndPage();
        }

        fclose(fp);
    }
        break;

    case FORMAT_TEX:
    {
        if(!FlGui::available()){
            Msg::Error("Creating '%s' requires a graphical interface context", name.c_str());
            break;
        }

        FILE *fp = Fopen(name.c_str(), "w");
        if(!fp){
            Msg::Error("Unable to open file '%s'", name.c_str());
            error = true;
            break;
        }
        std::string base = SplitFileName(name)[1];
        GLint width = FlGui::instance()->getCurrentOpenglWindow()->pixel_w();
        GLint height = FlGui::instance()->getCurrentOpenglWindow()->pixel_h();
        GLint pixel_viewport[4] = {0, 0, width, height};
        GLint buffsize = 0;
        int res = GL2PS_OVERFLOW;
        while(res == GL2PS_OVERFLOW) {
            buffsize += 2048 * 2048;
            gl2psBeginPage(base.c_str(), "Gmsh", pixel_viewport,
                           GL2PS_TEX, GL2PS_NO_SORT, GL2PS_NONE, GL_RGBA, 0, nullptr,
                           0, 0, 0, buffsize, fp, base.c_str());
            int oldtext = CTX::instance()->print.text;
            CTX::instance()->print.text = 1;
            drawContext::global()->drawCurrentOpenglWindow(true);
            CTX::instance()->print.text = oldtext;
            res = gl2psEndPage();
        }
        fclose(fp);
    }
        break;

    case FORMAT_PGF:
    {
        if(!FlGui::available()){
            Msg::Error("Creating '%s' requires a graphical interface context", name.c_str());
            break;
        }

        // fill pixel buffer without colorbar and axes
        int restoreGeneralAxis = (int) opt_general_axes(0, GMSH_GET, 0);
        int restoreSmallAxis = (int) opt_general_small_axes(0, GMSH_GET, 0);
        opt_general_axes(0, GMSH_SET, 0);
        opt_general_small_axes(0, GMSH_SET, 0);
        int num = -1; // id of the post view
        int cnt = 0; // no of scales/colorbars active
        for(std::size_t i = 0; i < opt_post_nb_views(0,GMSH_GET,0); i++) {
            if(opt_view_visible(i, GMSH_GET, 0)) {
                if (opt_view_show_scale(i, GMSH_GET, 0)) {
                    opt_view_show_scale(i, GMSH_SET, 0);
                    num = i; cnt++;
                }
            }
        }
        PixelBuffer *buffer = GetCompositePixelBuffer(GL_RGB, GL_UNSIGNED_BYTE);
        drawContext *ctx = FlGui::instance()->getCurrentOpenglWindow()->getDrawContext();
        GLint width = FlGui::instance()->getCurrentOpenglWindow()->pixel_w();
        GLint height = FlGui::instance()->getCurrentOpenglWindow()->pixel_h();
        GLint pixel_viewport[4] = {0, 0, width, height};
        print_pgf(name, num, cnt, buffer, ctx->r, pixel_viewport, ctx->proj, ctx->model);
        delete buffer;
        // restore view
        if(restoreGeneralAxis) opt_general_axes(0, GMSH_SET| GMSH_GUI, 1);
        if(restoreSmallAxis) opt_general_small_axes(0, GMSH_SET | GMSH_GUI, 1);
        if(cnt > 0) opt_view_show_scale(num, GMSH_SET, 1);
    }
        break;

#if defined(HAVE_MPEG_ENCODE)
    case FORMAT_MPEG:
    case FORMAT_MPEG_PREVIEW:
    {
        if(!FlGui::available()){
            Msg::Error("Creating '%s' requires a graphical interface context", name.c_str());
            break;
        }

        std::string parFileName = CTX::instance()->homeDir + ".gmsh-mpeg_encode.par";
        FILE *fp = 0;
        if(format != FORMAT_MPEG_PREVIEW){
            fp = Fopen(parFileName.c_str(), "w");
            if(!fp){
                Msg::Error("Unable to open file '%s'", parFileName.c_str());
                error = true;
                break;
            }
        }

        int numViews = (int)opt_post_nb_views(0, GMSH_GET, 0);
        int numSteps = 0;
        int cycle = CTX::instance()->post.animCycle;
        if(cycle == 0){
            for(int i = 0; i < numViews; i++){
                if(opt_view_visible(i, GMSH_GET, 0))
                    numSteps = std::max(numSteps,
                                        (int)opt_view_nb_non_empty_timestep(i, GMSH_GET, 0));
            }
        }
        else if(cycle == 1){
            numSteps = numViews;
        }
        else{
            numSteps = CTX::instance()->print.parameterSteps;
        }

        std::vector<std::string> frames;
        for(int i = 0; i < numSteps; i += CTX::instance()->post.animStep){
            char tmp[256];
            sprintf(tmp, ".gmsh-%06d.ppm", (int)frames.size());
            frames.push_back(tmp);
        }
        if(cycle != 2)
            status_play_manual(!cycle, 0, false);
        for(std::size_t i = 0; i < frames.size(); i++){
            if(cycle == 2)
                change_print_parameter(i);
            if(fp)
                CreateOutputFile(CTX::instance()->homeDir + frames[i], FORMAT_PPM,
                                 false, false);
            else{
                drawContext::global()->draw();
                SleepInSeconds(CTX::instance()->post.animDelay);
            }
            if(cycle != 2)
                status_play_manual(!cycle, CTX::instance()->post.animStep, false);
        }
        if(fp){
            int repeat = (int)(CTX::instance()->post.animDelay * 30);
            if(repeat < 1) repeat = 1;
            std::string pattern("I");
            // including P frames would lead to smaller files, but the quality
            // degradation is perceptible:
            // for(int i = 1; i < repeat; i++) pattern += "P";
            fprintf(fp, "PATTERN %s\nBASE_FILE_FORMAT PPM\nGOP_SIZE %d\n"
                        "SLICES_PER_FRAME 1\nPIXEL FULL\nRANGE 10\n"
                        "PSEARCH_ALG EXHAUSTIVE\nBSEARCH_ALG CROSS2\n"
                        "IQSCALE 1\nPQSCALE 1\nBQSCALE 25\nREFERENCE_FRAME DECODED\n"
                        "OUTPUT %s\nINPUT_CONVERT *\nINPUT_DIR %s\nINPUT\n",
                    pattern.c_str(), repeat, name.c_str(),
                    CTX::instance()->homeDir.c_str());
            for(std::size_t i = 0; i < frames.size(); i++){
                fprintf(fp, "%s", frames[i].c_str());
                if(repeat > 1) fprintf(fp, " [1-%d]", repeat);
                fprintf(fp, "\n");
            }
            fprintf(fp, "END_INPUT\n");
            fclose(fp);
            extern int mpeg_encode_main(int, char**);
            char *args[] = {(char*)"gmsh", (char*)parFileName.c_str()};
            try{
                mpeg_encode_main(2, args);
            }
            catch (const char *msg){
                Msg::Error("%s", msg);
                error = true;
            }
            if(opt_print_delete_tmp_files(0, GMSH_GET, 0)){
                UnlinkFile(parFileName);
                for(std::size_t i = 0; i < frames.size(); i++)
                    UnlinkFile(CTX::instance()->homeDir + frames[i]);
            }
        }
    }
        break;
#endif

#endif

    default:
        Msg::Error("Unknown output file format");
        error = true;
        break;
    }

    CTX::instance()->print.fileFormat = oldFormat;
    CTX::instance()->printing = 0;

    if(status && !error)
        Msg::StatusBar(true, "Done writing '%s'", name.c_str());

#if defined(HAVE_OPENGL)
    if(redraw) drawContext::global()->draw();
#endif
}
